package com.evil.log.service;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.evil.common.core.enums.BaseEnum;
import com.evil.common.family.constant.AppConstant;
import com.evil.common.family.enums.RCodeEnum;
import com.evil.common.family.enums.SpecialFieldEnum;
import com.evil.common.core.enums.SwitchEnum;
import com.evil.common.core.exception.BusinessException;
import com.evil.common.redis.util.RedisUtil;
import com.evil.common.core.util.StreamUtil;
import com.evil.common.db.mapper.BatchMapper;
import com.evil.common.log.constant.LogRedisConstant;
import com.evil.common.log.entity.LogContent;
import com.evil.common.log.entity.Operator;
import com.evil.common.log.enums.OperationTypeEnum;
import com.evil.common.log.enums.base.LogType;
import com.evil.common.log.util.LogRedisKeyUtil;
import com.evil.common.log.util.LogUtil;
import com.evil.log.pojo.entiy.OperationLog;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;

/**
 * 操作日志
 *
 * @author liyang
 * @date 2022-08-08 00:00
 */
@Slf4j
@AllArgsConstructor
public abstract class LogService {

    private final RedisUtil redisUtil;

    public abstract <T, ID extends Serializable, M extends BaseMapper<T>> void saveAndCheck(M mapper, Function<T, ID> getId, T... bean);

    public abstract <T, ID extends Serializable, M extends BaseMapper<T>> void saveAndCheck(M mapper, Function<T, ID> getId, Collection<T> beans);

    public abstract <T, ID extends Serializable, M extends BaseMapper<T>> int save(M mapper, Function<T, ID> getId, T... bean);

    public abstract <T, ID extends Serializable, M extends BaseMapper<T>> int save(M mapper, Function<T, ID> getId, Collection<T> beans);

    public abstract <T, ID extends Serializable, M extends BatchMapper<T, ID>> void saveBatchAndCheck(M batchMapper, Function<T, ID> getId, Collection<T> beans);

    /**
     * 保存
     *
     * @param mapper   DB接口
     * @param getId    getId 获取主键方法
     * @param beans    beans 实体列表
     * @param logType  操作日志类型
     * @param operator 操作日
     * @param <T>      实体泛型
     * @param <ID>     主键泛型
     * @return 成功数
     */
    public synchronized <T, ID extends Serializable, M extends BaseMapper<T>> int save(M mapper, Function<T, ID> getId, Collection<T> beans, LogType logType, Operator operator) {
        String requestLogId = LogUtil.getRequestLogId(redisUtil);

        AtomicInteger operateNum = new AtomicInteger();
        StreamUtil.groupK(beans, b -> Objects.isNull(getId.apply(b))).forEach((noId, list) -> {
            if (noId) {
                // 新增
                list.forEach(b -> {
                    // 创建人录入
                    this.setEntityCreatorAndModify(b, operator, true);
                    // 修改人录入
                    this.setEntityCreatorAndModify(b, operator, false);

                    // 插入
                    operateNum.addAndGet(mapper.insert(b));

                    // 操作日志
                    LogContent<T> content = new LogContent<>(null, b);
                    this.addOperationLog(requestLogId, logType, content, getId.apply(b), OperationTypeEnum.INSERT, operator, new Date());
                });
            } else {
                // 编辑
                List<ID> ids = StreamUtil.transListT(list, getId);
                Map<ID, T> tMap = StreamUtil.toMapK(mapper.selectBatchIds(ids), getId);
                // 遍历处理变动
                list.forEach(b -> {
                    // 修改人录入
                    this.setEntityCreatorAndModify(b, operator, false);

                    ID id = getId.apply(b);
                    if (!tMap.containsKey(id)) {
                        log.error("save entity not found, className:{}, id:{}", b.getClass().getName(), id);
                        throw new BusinessException(BaseEnum.RUN_ERROR);
                    }
                    T old = tMap.get(id);

                    // 更新
                    operateNum.addAndGet(mapper.updateById(b));

                    // 操作日志
                    LogContent<T> content = new LogContent<>();
                    content.setBefore(old);
                    content.setAfter(b);
                    OperationTypeEnum operationTypeEnum = this.isDeleted(b) ? OperationTypeEnum.DELETE : OperationTypeEnum.UPDATE;
                    this.addOperationLog(requestLogId, logType, content, getId.apply(b), operationTypeEnum, operator, new Date());
                });
            }
        });
        return operateNum.get();
    }

    /**
     * 批量保存
     *
     * @param batchMapper 具有批量保存的DB接口
     * @param getId       getId 获取主键方法
     * @param beans       beans 实体列表
     * @param logType     操作日志类型
     * @param operator    操作日
     * @param <T>         实体泛型
     * @param <ID>        主键泛型
     * @return 成功数
     */
    public synchronized <T, ID extends Serializable, M extends BatchMapper<T, ID>> int saveBatch(M batchMapper, Function<T, ID> getId, Collection<T> beans, LogType logType, Operator operator) {
        String requestLogId = LogUtil.getRequestLogId(redisUtil);

        List<T> modifies = new ArrayList<>();
        List<T> adds = new ArrayList<>();
        // 操作日志缓冲列表，用于后面补充ID
        List<OperationLogAndContent<T>> operationLogAndContents = new ArrayList<>();

        StreamUtil.groupK(beans, b -> Objects.isNull(getId.apply(b))).forEach((noId, list) -> {
            if (noId) {
                // 新增
                list.forEach(b -> {
                    // 创建人录入
                    this.setEntityCreatorAndModify(b, operator, true);
                    // 修改人录入
                    this.setEntityCreatorAndModify(b, operator, false);

                    adds.add(b);
                    // 记录缓冲日志列表
                    if (StringUtils.isNotBlank(requestLogId)) {
                        LogContent<T> content = new LogContent<>(null, b);
                        operationLogAndContents.add(new OperationLogAndContent<>(OperationTypeEnum.INSERT, content));
                    }
                });
            } else {
                // 编辑
                List<ID> ids = StreamUtil.transListT(list, getId);
                Map<ID, T> tMap = StreamUtil.toMapK(batchMapper.findByIds(ids, false), getId);
                // 遍历处理变动
                list.forEach(b -> {
                    // 修改人录入
                    this.setEntityCreatorAndModify(b, operator, false);

                    ID id = getId.apply(b);
                    if (!tMap.containsKey(id)) {
                        log.error("save entity not found, className:{}, id:{}", b.getClass().getName(), id);
                        throw new BusinessException(BaseEnum.RUN_ERROR);
                    }
                    T old = tMap.get(id);

                    modifies.add(b);
                    // 记录缓冲日志列表
                    if (StringUtils.isNotBlank(requestLogId)) {
                        OperationTypeEnum operationTypeEnum = this.isDeleted(b) ? OperationTypeEnum.DELETE : OperationTypeEnum.UPDATE;
                        LogContent<T> content = new LogContent<>(old, b);
                        operationLogAndContents.add(new OperationLogAndContent<>(operationTypeEnum, content));
                    }
                });
            }
        });

        int operationNum = 0;
        if (!modifies.isEmpty()) {
            operationNum += this.updateBatch(batchMapper, modifies);
        }
        if (!adds.isEmpty()) {
            operationNum += this.batchInsert(batchMapper, adds);
        }

        Date optime = new Date();
        operationLogAndContents.forEach(o -> {
            ID id = getId.apply(o.logContent.getAfter());
            this.addOperationLog(requestLogId, logType, o.logContent, id, o.operationTypeEnum, operator, optime);
        });
        return operationNum;
    }

    /**
     * 批量更新
     *
     * @param batchMapper 具有批量保存的DB接口
     * @param beans       beans 实体列表
     * @param <T>         实体泛型
     * @param <ID>        主键泛型
     */
    public <T, ID extends Serializable, M extends BatchMapper<T, ID>> void updateBatchAndCheck(M batchMapper, Collection<T> beans) {
        if (!beans.isEmpty()) {
            if (beans.size() != this.updateBatch(batchMapper, beans)) {
                throw new BusinessException(RCodeEnum.UPDATE_ERROR);
            }
        }
    }

    /**
     * 批量更新
     *
     * @param batchMapper 具有批量保存的DB接口
     * @param beans       beans 实体列表
     * @param <T>         实体泛型
     * @param <ID>        主键泛型
     */
    public <T, ID extends Serializable, M extends BatchMapper<T, ID>> int updateBatch(M batchMapper, Collection<T> beans) {
        return CollUtil.split(beans, AppConstant.INSERT_DATABASE).stream().mapToInt(batchMapper::updateBatch).sum();
    }

    /**
     * 批量插入
     *
     * @param batchMapper 具有批量保存的DB接口
     * @param beans       beans 实体列表
     * @param <T>         实体泛型
     * @param <ID>        主键泛型
     */
    public <T, ID extends Serializable, M extends BatchMapper<T, ID>> void batchInsertAndCheck(BatchMapper<T, ID> batchMapper, Collection<T> beans) {
        if (!beans.isEmpty()) {
            if (beans.size() != this.batchInsert(batchMapper, beans)) {
                throw new BusinessException(RCodeEnum.UPDATE_ERROR);
            }
        }
    }

    /**
     * 批量插入
     *
     * @param batchMapper 具有批量保存的DB接口
     * @param beans       beans 实体列表
     * @param <T>         实体泛型
     * @param <ID>        主键泛型
     */
    public <T, ID extends Serializable, M extends BatchMapper<T, ID>> int batchInsert(BatchMapper<T, ID> batchMapper, Collection<T> beans) {
        return CollUtil.split(beans, AppConstant.INSERT_DATABASE).stream().mapToInt(batchMapper::batchInsert).sum();
    }

    /**
     * 添加操作日志内容：目前使用暂存redis
     *
     * @param requestLogId      requestLogId
     * @param logType           logType
     * @param content           content
     * @param id                id
     * @param operationTypeEnum operationTypeEnum
     * @param operator          operator
     * @param optime            optime
     * @param <T>               T
     * @param <ID>              ID
     */
    private <T, ID extends Serializable> void addOperationLog(String requestLogId, LogType logType, LogContent<T> content, ID id, OperationTypeEnum operationTypeEnum, Operator operator, Date optime) {
        if (StringUtils.isBlank(requestLogId)) {
            return;
        }
        OperationLog operationLog = new OperationLog();
        operationLog.setRequestLogId(Long.valueOf(requestLogId));
        operationLog.setLogType(logType.getType());
        operationLog.setLogContent(JSON.toJSONString(content));
        operationLog.setContentId(String.valueOf(id));
        operationLog.setType(operationTypeEnum.getTitle());
        operationLog.setTime(optime);
        operationLog.setCreatorId(operator.getId());
        operationLog.setCreatorName(operator.getName());
        operationLog.setCreated(new Date());
        redisUtil.sSetAndTime(LogRedisKeyUtil.logKey(operationLog.getRequestLogId()), LogRedisConstant.LOG_KEY_EXPIRE_IN_SECOND, operationLog);
    }

    /**
     * 更新 创建人|修改人
     *
     * @param b        实体
     * @param operator 操作人
     * @param <T>      实体
     */
    private <T> void setEntityCreatorAndModify(T b, Operator operator, boolean creator) {
        try {
            Field idField = b.getClass().getDeclaredField(creator ? SpecialFieldEnum.CREATOR_ID.getName() : SpecialFieldEnum.MODIFIER_ID.getName());
            Field nameField = b.getClass().getDeclaredField(creator ? SpecialFieldEnum.CREATOR_NAME.getName() : SpecialFieldEnum.MODIFIER_NAME.getName());
            Field updatedField = b.getClass().getDeclaredField(SpecialFieldEnum.UPDATED.getName());
            idField.setAccessible(true);
            nameField.setAccessible(true);
            updatedField.setAccessible(true);
            try {
                if (creator) {
                    Field createdField = b.getClass().getDeclaredField(SpecialFieldEnum.CREATED.getName());
                    createdField.setAccessible(true);
                    createdField.set(b, new Date());
                }
                idField.set(b, operator.getId());
                nameField.set(b, operator.getName());
                updatedField.set(b, new Date());
            } catch (IllegalAccessException e) {
                log.error("保存实体：{}，失败，该实体有[{}]字段但是设置修改人失败", b.getClass().getSimpleName(), creator ? "创建人" : "修改人");
                throw new BusinessException(BaseEnum.RUN_ERROR);
            }
        } catch (NoSuchFieldException ignored) {
        }
    }

    /**
     * 是否为已删除
     *
     * @param b   实体
     * @param <T> 实体
     * @return boolean
     */
    private <T> boolean isDeleted(T b) {
        try {
            Field isDeletedField = b.getClass().getDeclaredField(SpecialFieldEnum.IS_DELETED.getName());
            isDeletedField.setAccessible(true);
            Object isDeletedData = isDeletedField.get(b);
            if (ObjectUtils.isNotEmpty(isDeletedData)) {
                return SwitchEnum.isOpen((Integer) isDeletedData);
            }
        } catch (NoSuchFieldException | SecurityException | IllegalAccessException ignored) {
        }
        return false;
    }

    /**
     * 获取实体 主键字段属性
     *
     * @param b   b
     * @param <T> T
     * @return Field
     */
    public <T> Field getEntityIdField(T b) {
        Field tableIdField = Arrays.stream(b.getClass().getDeclaredFields())
                .filter(e -> e.isAnnotationPresent(TableId.class)).findFirst()
                .orElseThrow(() -> new BusinessException(BaseEnum.RUN_ERROR));
        // 取消语言访问检查
        tableIdField.setAccessible(true);
        return tableIdField;
    }

    /**
     * 获取实体 主键
     *
     * @param b   实体
     * @param <T> 实体
     * @return Object
     */
    private <T> Object getEntityId(T b, Field tableIdField) {
        try {
            return tableIdField.get(b);
        } catch (IllegalAccessException e) {
            log.error("保存实体：{}，失败，获取主键失败", b.getClass().getSimpleName());
            throw new BusinessException(BaseEnum.RUN_ERROR);
        }
    }

    @AllArgsConstructor
    private static class OperationLogAndContent<T> {
        private final OperationTypeEnum operationTypeEnum;
        private final LogContent<T> logContent;
    }
}
